package com.basker.pisces.core.helper;

import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.DataContractGenerator;
import com.basker.pisces.core.IDataContract;
import com.basker.pisces.core.IDataContractCollection;
import com.basker.pisces.core.IInitialize;
import com.basker.pisces.core.enums.DataObjectState;
import com.basker.pisces.core.exception.ReadOnlyObjectModifyException;
import com.basker.pisces.core.meta.ICollectionField;
import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IDataObjectField;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.IReferenceField;
import com.basker.pisces.core.meta.annotation.DataObjectMeta;
import com.basker.pisces.core.meta.bean.BeanMetaManager;
import com.basker.pisces.utils.ReflectionUtils;

/**
 * 数据对象的帮助类
 *
 * @author hangwen
 */
public class DataContractHelper {

    /**
     * 断言：对象不是只读的
     *
     * @param dataObject
     */
    public static void assertNotReadOnly(Object dataObject) {
        if (isReadOnly(dataObject)) {
            throw new ReadOnlyObjectModifyException(DataContractHelper.toString(dataObject));
        }
    }

    /**
     * 按照元数据和选项参数复制对象
     *
     * @param objectMeta
     * @param source
     * @param option
     * @return
     */
    public static <T> T copy(IDataObjectMeta objectMeta, T source, DataContractCopyOption option) {
        return copy(null, objectMeta, source, option);
    }

    /**
     * 复制对象
     *
     * @param source
     * @return
     */
    public static <T> T copy(T source) {
        if (source == null) {
            return null;
        }

        return copy(null, BeanMetaManager.getBeanMeta(source.getClass()), source,
                DataContractCopyOption.DEFAULT_OPTION);
    }

    /**
     * 按照选项参数复制对象
     *
     * @param source
     * @param option
     * @return
     */
    public static <T> T copy(T source, DataContractCopyOption option) {
        if (source == null) {
            return null;
        }

        return copy(null, BeanMetaManager.getBeanMeta(source.getClass()), source, option);
    }

    public static IDataObjectMeta getDataObjectMeta(Object dataObject) {
        Assert.notNull(dataObject, "parameter 'dataObject' is required");
        return BeanMetaManager.getBeanMeta(dataObject.getClass());
    }

    /**
     * 判断对象是否发生变更，只对从数据库读出的对象有效，否则一律返回false.
     * <p>
     * 是否发生变更，不单取决于聚合根，即使根对象没有字段被修改过，但是其子对象发生增删改的话，也会被认为发生变更。
     *
     * @param objectMeta
     * @param dataObject
     * @return
     */
    public static boolean isChanged(IDataObjectMeta objectMeta, Object dataObject) {
        if (DataObjectState.Modified.equals(objectMeta.getState(dataObject))) {
            return true;
        }

        Collection<ICollectionField> collectionFields = objectMeta.getCollectionFields();
        if (CollectionUtils.isEmpty(collectionFields)) {
            return false;
        }

        for (ICollectionField collectionField : collectionFields) {
            IDataObjectMeta itemObjectMeta = collectionField.getItemObjectMeta();
            Collection<Object> childObjectList = collectionField.getValue(dataObject);

            if (childObjectList instanceof IDataContractCollection) {
                List<IDataContract> removedObjects = ((IDataContractCollection) childObjectList).getRemovedObjects();
                if (!CollectionUtils.isEmpty(removedObjects)) {
                    // 子表有删除
                    return true;
                }
            }

            // 集合如果为空，说明没有新增和修改的，判断下一个
            if (CollectionUtils.isEmpty(childObjectList)) {
                continue;
            }

            boolean hasNewChildObject = childObjectList.stream()
                    .anyMatch(childObject -> !itemObjectMeta.isFromDB(childObject));
            if (hasNewChildObject) {
                // 子表有新增
                return true;
            }

            boolean hasModifiedChildObject = childObjectList.stream()
                    .anyMatch(childObject -> isChanged(itemObjectMeta, childObject));
            if (hasModifiedChildObject) {
                // 至少一个子对象有修改
                return true;
            }
        }

        return false;
    }

    /**
     * 判断对象是否发生变更，只对从数据库读出的对象有效，否则一律返回false.
     * <p>
     * 是否发生变更，不单取决于聚合根，即使根对象没有字段被修改过，但是其子对象发生增删改的话，也会被认为发生变更。
     *
     * @param dataObject
     * @return
     */
    public static boolean isChanged(Object dataObject) {
        if (dataObject instanceof IDataContract) {
            return isChanged(getDataObjectMeta(dataObject), dataObject);
        }

        return false;
    }

    /**
     * 判断对象的指定字段是否发生变更，只对从数据库读出的对象，否则一律返回false
     *
     * @param dataObject
     * @param field
     * @return
     */
    public static boolean isChanged(Object dataObject, String fieldName) {
        if (dataObject instanceof IDataContract) {
            return ((IDataContract) dataObject).isChanged(fieldName);
        }

        return false;
    }

    /**
     * 判断对象是否从数据库加载
     *
     * @param dataObject
     * @return
     */
    public static boolean isFromDB(Object dataObject) {
        if (dataObject instanceof IDataContract) {
            return ((IDataContract) dataObject).isFromDB();
        }

        return false;
    }

    /**
     * 判断对象是否处于初始化状态
     *
     * @param dataObject
     * @return
     */
    public static boolean isInInitial(Object dataObject) {
        if (dataObject instanceof IInitialize) {
            return ((IInitialize) dataObject).isInInitial();
        }

        return false;
    }

    /**
     * 判断对象是否只读
     *
     * @param dataObject
     * @return
     */
    public static boolean isReadOnly(Object dataObject) {
        if (dataObject instanceof IDataContract) {
            return ((IDataContract) dataObject).isReadOnly();
        }

        return false;
    }

    /**
     * 判断对象是否可以支持元数据
     *
     * @param dataObject
     * @return
     */
    public static boolean isSupportDataObjectMeta(Object dataObject) {
        return dataObject != null && dataObject.getClass().isAnnotationPresent(DataObjectMeta.class);
    }

    /**
     * 标记对象的只读属性，会递归标记其子对象
     *
     * @param dataObject
     * @param readOnly
     */
    public static void markReadOnly(Object dataObject, boolean readOnly) {
        if (!(dataObject instanceof IDataContract)) {
            return;
        }

        IDataContract dataContract = (IDataContract) dataObject;
        if (dataContract.isReadOnly() == readOnly) {
            return;
        }

        IDataObjectMeta objectMeta = BeanMetaManager.getBeanMeta(dataObject.getClass());
        if (!readOnly && objectMeta.isReadOnly()) {
            // 元数据设置为只读，不能将对象标记为可写
            return;
        }

        dataContract.setReadOnly(readOnly);

        for (ICollectionField field : objectMeta.getCollectionFields()) {
            @SuppressWarnings("unchecked")
            Collection<Object> list = (Collection<Object>) field.getValue(dataContract);

            if (!CollectionUtils.isEmpty(list)) {
                for (Object chidObject : list) {
                    markReadOnly(chidObject, readOnly);
                }
            }
        }
    }

    /**
     * 根据元数据和选项参数，将输入对象融合到目标对象，相当于将输入对象的属性赋值给目标对象,<b>但是输入对象的主键值和状态值是不会赋值给目标对象的</b>
     *
     * @param objectMeta
     * @param input
     * @param target
     * @param option
     */
    public static <T> void merge(IDataObjectMeta objectMeta, T input, T target, DataContractMergeOption option) {
        if (input == null || target == null) {
            return;
        }

        mergeDataFields(objectMeta, input, target, option);
        mergeObjectFields(objectMeta, input, target, option);

        if (option.isMergeChildren()) {
            throw new RuntimeException("merge children is not supported");
            // TODO
            // mergeCollectionFields(objectMeta, input, target, option);
        }
    }

    /**
     * 将输入对象融合到目标对象，相当于将输入对象的属性赋值给目标对象,<b>但是输入对象的主键值和状态值是不会赋值给目标对象的</b>,不会操作目标对象的子表
     *
     * @param input
     * @param target
     */
    public static <T> void merge(T input, T target) {
        if (input == null || target == null) {
            return;
        }

        merge(BeanMetaManager.getBeanMeta(input.getClass()), input, target, DataContractMergeOption.DEFAULT_OPTION);
    }

    /**
     * 根据选项参数，将输入对象融合到目标对象，相当于将输入对象的属性赋值给目标对象,<b>但是输入对象的主键值和状态值是不会赋值给目标对象的</b>
     *
     * @param input
     * @param target
     * @param option
     */
    public static <T> void merge(T input, T target, DataContractMergeOption option) {
        if (input == null || target == null) {
            return;
        }

        merge(BeanMetaManager.getBeanMeta(input.getClass()), input, target, option);
    }

    /**
     * 记录对象指定属性的旧值，如果对象是从数据库加载出来的，那么会标记其状态是{@link DataObjectState#Modified}
     *
     * @param dataObject
     * @param fieldName
     * @param oldValue
     */
    public static void recordOldValue(Object dataObject, String fieldName, Object oldValue) {
        if (dataObject instanceof IDataContract) {
            IDataContract dataContract = (IDataContract) dataObject;

            if (dataContract.isFromDB()) {
                dataContract.recordOldValue(fieldName, oldValue);
                dataContract.setState(DataObjectState.Modified);
            }
        }
    }

    public static String toString(Object dataObject) {
        return toString(null, dataObject);
    }

    public static String toString(String prefix, Object dataObject) {
        if (dataObject == null) {
            return "";
        }

        IDataObjectMeta meta = getDataObjectMeta(dataObject);
        IDataField codeField = meta.getCodeField();
        IDataField nameField = meta.getNameField();

        String code = "";
        if (codeField != null) {
            Object value = codeField.getValue(dataObject);
            if (value != null) {
                code = String.valueOf(value);
            }
        }

        String name = "";
        if (nameField != null) {
            Object value = nameField.getValue(dataObject);
            if (value != null) {
                name = String.valueOf(value);
            }
        }

        String result = prefix != null ? prefix + " " : "";

        if (code.equals(name) || StringUtils.isEmpty(name)) {
            return result += code;
        }

        if (StringUtils.isEmpty(code)) {
            return result += name;
        }

        return result += (code + "-" + name);

    }

    @SuppressWarnings("unchecked")
    private static <T> T copy(String prefixName, IDataObjectMeta objectMeta, T source, DataContractCopyOption option) {
        if (source == null) {
            return null;
        }

        Object newObj = null;

        if (source instanceof IDataContract) {
            try {
                DataContractGenerator.enable();
                newObj = objectMeta.createDataObject();
            } finally {
                DataContractGenerator.disable();
            }
        } else {
            newObj = objectMeta.createDataObject();
        }

        if (newObj instanceof IInitialize) {
            ((IInitialize) newObj).beginInit();
        }

        try {
            copyStatus(source, newObj, option);
            copyDataFields(prefixName, objectMeta, source, newObj, option);
            copyObjectFields(prefixName, objectMeta, source, newObj, option);
            copyCollectionFields(prefixName, objectMeta, source, newObj, option);
        } finally {
            if (newObj instanceof IInitialize) {
                ((IInitialize) newObj).endInit();
            }
        }

        return (T) newObj;
    }

    @SuppressWarnings("unchecked")
    private static <T> void copyCollectionFields(String prefixName, IDataObjectMeta objectMeta, T source, Object newObj,
                                                 DataContractCopyOption option) {
        // 获取当前数据契约中，所有关联集合的成员。
        Collection<ICollectionField> collectionFields = objectMeta.getCollectionFields();
        for (ICollectionField field : collectionFields) {
            if (option.ignoreField(prefixName, field.getName())) {
                continue;
            }

            Collection<Object> list = field.getValue(source);
            if (list == null) {
                continue;
            }

            Collection<Object> newList = (Collection<Object>) field.getValue(newObj);
            if (newList == null) {
                newList = ReflectionUtils.createNewInstance(list.getClass());
            }

            for (Object sourceChd : list) {
                IDataObjectMeta itemObjectMeta = field.getItemObjectMeta();
                if (itemObjectMeta == null) {
                    itemObjectMeta = BeanMetaManager.getBeanMeta(sourceChd.getClass());
                }

                String prefix2Use = prefixName != null ? (prefixName + ".") : "";
                prefix2Use += field.getName();

                Object newChd = copy(prefix2Use, itemObjectMeta, sourceChd, option);
                newList.add(newChd);
            }
        }
    }

    private static <T> void copyDataFields(String prefixName, IDataObjectMeta objectMeta, T source, Object newObj,
                                           DataContractCopyOption option) {
        // 获取所有数据成员
        Collection<IDataField> dataFields = objectMeta.getDataFields();
        for (IDataField field : dataFields) {
            if (field.isPkField() && !option.isCopyPkValue()) {
                continue;
            }

            if (option.ignoreField(prefixName, field.getName())) {
                continue;
            }

            Object value = field.getValue(source);
            field.setValue(newObj, value);
        }
    }

    private static <T> void copyObjectFields(String prefixName, IDataObjectMeta objectMeta, T source, Object newObj,
                                             DataContractCopyOption option) {
        // 获取当前数据契约中，所有关联对象的成员。
        Collection<IDataObjectField> objectFields = objectMeta.getDataObjectFields();
        for (IDataObjectField field : objectFields) {
            if (option.ignoreField(prefixName, field.getName())) {
                continue;
            }

            Object value = field.getValue(source);

            if (field instanceof IReferenceField) {
                // 引用成员不需要拷贝value对象，直接赋值
                field.setValue(newObj, value);
            } else if (value != null) {
                IDataObjectMeta refObjectMeta = field.getDataObjectMeta();
                if (refObjectMeta == null) {
                    refObjectMeta = BeanMetaManager.getBeanMeta(value.getClass());
                }

                String prefix2Use = prefixName != null ? (prefixName + ".") : "";
                prefix2Use += field.getName();

                value = copy(prefix2Use, refObjectMeta, value, option);
                field.setValue(newObj, value);
            }
        }
    }

    private static <T> void copyStatus(T source, Object newObj, DataContractCopyOption option) {
        if (option.isCopyDataContractStatus() && source instanceof IDataContract && newObj instanceof IDataContract) {
            IDataContract s = (IDataContract) source;
            IDataContract t = (IDataContract) newObj;

            t.setReadOnly(s.isReadOnly());
            t.setFromDB(s.isFromDB());
            t.setState(s.getState());

            Class<? extends IDataContract> cls = s.getClass();
            Field fromDBMapField = ReflectionUtils.getDeclaredField(cls, "fromDBMap");
            Map<?, ?> fromDBMap = (Map<?, ?>) ReflectionUtils.getFieldValue(fromDBMapField, s);

            if (fromDBMap != null) {
                ReflectionUtils.setFieldValue(fromDBMapField, t, new HashMap<>(fromDBMap));
            }
        }
    }

    private static <T> void mergeDataFields(IDataObjectMeta objectMeta, T input, T target,
                                            DataContractMergeOption option) {
        // 获取所有数据成员
        Collection<IDataField> dataFields = objectMeta.getDataFields();
        for (IDataField field : dataFields) {
            if (field.isPkField()) {
                continue;
            }

            Object value = field.getValue(input);
            if (value == null && !option.isSetNullValue()) {
                // 忽略null值
                continue;
            }

            field.setValue(target, value);
        }
    }

    private static <T> void mergeObjectFields(IDataObjectMeta objectMeta, T input, T target,
                                              DataContractMergeOption option) {
        Collection<IDataObjectField> objectFields = objectMeta.getDataObjectFields();
        for (IDataObjectField field : objectFields) {
            Object inputValue = field.getValue(input);

            if (inputValue == null && !option.isSetNullValue()) {
                // 忽略null值
                continue;
            }

            if (field instanceof IReferenceField) {
                // 引用成员不需要拷贝value对象，直接赋值
                field.setValue(target, inputValue);
            } else if (inputValue != null) {
                IDataObjectMeta refObjectMeta = field.getDataObjectMeta();
                if (refObjectMeta == null) {
                    refObjectMeta = BeanMetaManager.getBeanMeta(inputValue.getClass());
                }

                Object targetValue = field.getValue(target);
                if (targetValue == null) {
                    DataContractCopyOption copyOption = new DataContractCopyOption();
                    copyOption.setCopyPkValue(true);
                    targetValue = copy(refObjectMeta, inputValue, copyOption);

                    field.setValue(target, targetValue);
                } else {
                    merge(refObjectMeta, inputValue, targetValue, option);
                }
            } else {
                field.setValue(target, null);
            }
        }
    }

    /**
     * 复制对象的选项参数
     *
     * @author hangwen
     */
    public static class DataContractCopyOption {

        /**
         * 默认选项，不拷贝主键，对象状态为初始化
         */
        public static final DataContractCopyOption DEFAULT_OPTION = new DataContractCopyOption();

        private boolean copyPkValue = false;
        private boolean copyDataContractStatus = false;
        private Set<String> ignoreFields;

        public DataContractCopyOption() {
            this(null);
        }

        /**
         * 设置忽略的成员
         *
         * <li>如果需要忽略关联对象的成员以及子对象的成员的话，memeberName需要加上父成员名作为前缀
         *
         * @param ignoreFields
         */
        public DataContractCopyOption(Collection<String> ignoreFields) {
            if (!CollectionUtils.isEmpty(ignoreFields)) {
                this.ignoreFields = new HashSet<String>(ignoreFields);
            }
        }

        /**
         * 是否需要忽略当前字段
         *
         * @param prefix
         * @param fieldName
         * @return
         */
        public boolean ignoreField(String prefix, String fieldName) {
            if (CollectionUtils.isEmpty(ignoreFields)) {
                return false;
            }

            String fieldToUse = fieldName;
            if (!StringUtils.isEmpty(prefix)) {
                fieldToUse = MessageFormat.format("{0}.{1}", prefix, fieldName);
            }

            return ignoreFields.contains(fieldToUse);
        }

        /**
         * 是否复制状态字段：只读、对象持久化状态等
         *
         * @return
         */
        public boolean isCopyDataContractStatus() {
            return copyDataContractStatus;
        }

        /**
         * 是否复制主键
         *
         * @return
         */
        public boolean isCopyPkValue() {
            return copyPkValue;
        }

        /**
         * @param copyDataContractStatus
         * @see #isCopyDataContractStatus()
         */
        public void setCopyDataContractStatus(boolean copyDataContractStatus) {
            this.copyDataContractStatus = copyDataContractStatus;
        }

        /**
         * @param copyPkValue
         * @see #isCopyPkValue()
         */
        public void setCopyPkValue(boolean copyPkValue) {
            this.copyPkValue = copyPkValue;
        }
    }

    /**
     * 融合对象的选项参数
     *
     * @author hangwen
     */
    public static class DataContractMergeOption {
        /**
         * 默认选项，输入对象的null值不会赋给目标对象,不会处理子表
         */
        public static final DataContractMergeOption DEFAULT_OPTION = new DataContractMergeOption();

        private boolean setNullValue = false;
        private boolean mergeChildren = false;

        /**
         * 是否融合子对象
         *
         * @return
         */
        public boolean isMergeChildren() {
            return mergeChildren;
        }

        /**
         * 是否将null赋值给目标对象，默认false
         *
         * @return
         */
        public boolean isSetNullValue() {
            return setNullValue;
        }

        /**
         * @param mergeChildren
         * @see #isMergeChildren()
         */
        public void setMergeChildren(boolean mergeChildren) {
            this.mergeChildren = mergeChildren;
        }

        /**
         * @param setNullValue
         * @see #isSetNullValue()
         */
        public void setSetNullValue(boolean setNullValue) {
            this.setNullValue = setNullValue;
        }
    }
}
